﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using MetaCollection.Framework;
using Model = MetaCollection.Framework.Model;

namespace MetaCollection.ContentService.WebDav.Methods
{
	/// <summary>
	/// The PROPFIND method retrieves properties defined on the resource
	/// identified by the Request-URI, if the resource does not have any
	/// internal members, or on the resource identified by the Request-URI
	/// and potentially its member resources, if the resource is a collection
	/// that has internal member URLs.  All DAV-compliant resources MUST
	/// support the PROPFIND method and the propfind XML element
	/// (Section 14.20) along with all XML elements defined for use with that
	/// element.
	/// 
	/// A client MUST submit a Depth header with a value of "0", "1", or
	/// "infinity" with a PROPFIND request.  Servers MUST support "0" and "1"
	/// depth requests on WebDAV-compliant resources and SHOULD support
	/// "infinity" requests.
	/// </summary>
	/// <example>
	///   >>Request
	///
	///     PROPFIND /file HTTP/1.1
	///     Host: www.example.com
	///     Content-type: application/xml; charset="utf-8"
	///     Content-Length: xxxx
	///
	///     <?xml version="1.0" encoding="utf-8" ?>
	///     <D:propfind xmlns:D="DAV:">
	///       <D:prop xmlns:R="http://ns.example.com/boxschema/">
	///         <R:bigbox/>
	///         <R:author/>
	///         <R:DingALing/>
	///         <R:Random/>
	///       </D:prop>
	///     </D:propfind>
	///
	///
	///   >>Response
	///
	///     HTTP/1.1 207 Multi-Status
	///     Content-Type: application/xml; charset="utf-8"
	///     Content-Length: xxxx
	///
	///     <?xml version="1.0" encoding="utf-8" ?>
	///     <D:multistatus xmlns:D="DAV:">
	///       <D:response xmlns:R="http://ns.example.com/boxschema/">
	///         <D:href>http://www.example.com/file</D:href>
	///         <D:propstat>
	///           <D:prop>
	///             <R:bigbox>
	///               <R:BoxType>Box type A</R:BoxType>
	///             </R:bigbox>
	///             <R:author>
	///               <R:Name>J.J. Johnson</R:Name>
	///             </R:author>
	///           </D:prop>
	///           <D:status>HTTP/1.1 200 OK</D:status>
	///         </D:propstat>
	///         <D:propstat>
	///           <D:prop><R:DingALing/><R:Random/></D:prop>
	///           <D:status>HTTP/1.1 403 Forbidden</D:status>
	///           <D:responsedescription> The user does not have access to the
	///      DingALing property.
	///           </D:responsedescription>
	///         </D:propstat>
	///       </D:response>
	///       <D:responsedescription> There has been an access violation error.
	///       </D:responsedescription>
	///     </D:multistatus>
	/// 
	/// </example>
	/// <remarks>
	/// In order to protect the system from Denial of Service attacks this
	/// implementation will NOT support the depth infinity header and will
	/// treat missing Depth header requests as Depth:1 in order to conserve
	/// server resources
	/// -- from the RFC4918 ---
	/// A server MAY reject PROPFIND requests on collections with depth header
	/// of "Infinity", in which case it SHOULD use the 403 Forbidden error with
	/// the precondition code 'propfind-finite-depth' inside the error body.
	/// 
	/// --- Additional Information On Content Service Format of Responses ---
	/// 
	/// For Folders "PROPFIND - allprop" will return basic item properties
	/// 
	/// For Items "PROPFIND - allprop" will return all properties with at least
	/// their names.  Some properties will only return values if they are in
	/// the include list.
	///   - Items will include a Version listing that has basic version info
	///   for each item in a Version property. (Note: should match some sort of 
	///   versioning REPORT?)
	///   - Items with a property of ContentService.AllowsChildren will have a
	///   listing of items with basic property info (similar to a folder) that
	///   follows the detailed property info.
	///   - Items will include an inbound relationship property which gets
	///   its data via a FindByRelationship search for relationships targeting
	///   the given item.
	/// </remarks>
	public static class PropFind
	{
		public static void Respond(DavContext context, IRequestHandler handler)
		{
			IEnumerable<XElement> responseData;
			context.Response.SetCacheHeaders(HttpCacheability.ServerAndPrivate, DateTime.Now);
			
			responseData = BuildResponse(context, handler);
			context.Response.OutputXml(responseData);
		}

		public static IEnumerable<XElement> BuildResponse(DavContext context, IRequestHandler handler)
		{
			DepthOption depth = context.Request.Depth();
			List<XName> requestedProperties = Properties.RequestedProperties(context.Request.Xml);
			Model.LoadOptions derivedLoadOptions = Properties.LoadOptionsFromRequestedProperties(requestedProperties);
			IEnumerable<XElement> responseData = context.Item.DavData(derivedLoadOptions).PropInfoResponse(context, requestedProperties);

			if (depth == DepthOption.Undefined || depth == DepthOption.ImmediateDescendants)
			{
				IEnumerable<Model.IItemInstance> items = handler.ItemRequest(context.User, RequestTypes.GetItem | RequestTypes.ProcessDirectDescendents, context.Item.Href, derivedLoadOptions);
				return responseData.Concat(items.DavData(derivedLoadOptions).PropInfoResponse(context, requestedProperties.Count < 1 ? Properties.BasicNames : requestedProperties));
			}
			else if (depth == DepthOption.AllDescendants)
			{
				IEnumerable<Model.IItemInstance> items = handler.ItemRequest(context.User, RequestTypes.GetItem | RequestTypes.ProcessAllDescendents, context.Item.Href, derivedLoadOptions);
				return responseData.Concat(items.DavData(derivedLoadOptions).PropInfoResponse(context, requestedProperties.Count < 1 ? Properties.BasicNames : requestedProperties));
			}
			else
				return responseData;
		}
	}
}
